package dao

import (
	"context"
	rentalpb "coolcar/v1/rental/api/gen/v1"
	"coolcar/v1/shared/ids"
	"coolcar/v1/shared/mgutils"
	"coolcar/v1/shared/mgutils/objid"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
)

const (
	tripField      = "trip"
	accountIDField = tripField + ".accountid"
	statusField    = tripField + ".status"
)

// Mongo defines the Mongo database.
type Mongo struct {
	col *mongo.Collection
}

// NewMongo creates a new Mongo dao.
func NewMongo(db *mongo.Database) *Mongo {
	return &Mongo{col: db.Collection(tripField)}
}

// TripRecord defines a trip record.
type TripRecord struct {
	mgutils.IDField        `bson:"inline"`
	mgutils.UpdatedAtField `bson:"inline"`
	Trip                   *rentalpb.Trip `bson:"trip"`
}

// CreateTrip creates a new trip record.
func (m *Mongo) CreateTrip(ctx context.Context, trip *rentalpb.Trip) (*TripRecord, error) {
	r := &TripRecord{
		Trip: trip,
	}
	r.ID = mgutils.NewObjID()
	r.UpdatedAt = mgutils.UpdatedAt()
	_, err := m.col.InsertOne(ctx, r)
	if err != nil {
		return nil, err
	}
	return r, nil
}

func (m *Mongo) GetTrip(ctx context.Context, aid ids.AccountID, tid ids.TripID) (*TripRecord, error) {
	objectID := objid.MustFromID(tid)
	filter := bson.M{
		mgutils.IDFieldName: objectID,
		accountIDField:      aid.String(),
	}
	result := m.col.FindOne(ctx, filter)
	if err := result.Err(); err != nil {
		return nil, err
	}

	var tr TripRecord
	err := result.Decode(&tr)
	if err != nil {
		return nil, err
	}
	return &tr, nil
}

// GetTrips gets trips for the account by status.
// IF status not specified, gets all trips.
func (m *Mongo) GetTrips(ctx context.Context, aid ids.AccountID, status rentalpb.TripStatus) ([]*TripRecord, error) {
	filter := bson.M{
		accountIDField: aid.String(),
	}
	if status != rentalpb.TripStatus_TS_NOT_SPECIFIED {
		filter[statusField] = status
	}
	res, err := m.col.Find(ctx, filter)
	if err != nil {
		return nil, err
	}
	var trs []*TripRecord
	for res.Next(ctx) {
		var trip TripRecord
		err := res.Decode(&trip)
		if err != nil {
			return nil, err
		}
		trs = append(trs, &trip)
	}
	return trs, nil
}

// UpdateTrip update a trip.
func (m *Mongo) UpdateTrip(ctx context.Context, id ids.TripID, aid ids.AccountID, updatedAt int64, trip *rentalpb.Trip) error {
	objectID := objid.MustFromID(id)
	newUpdatedAt := mgutils.UpdatedAt()
	filter := bson.M{
		mgutils.IDFieldName:      objectID,
		accountIDField:           aid.String(),
		mgutils.UpdatedFieldName: updatedAt,
	}
	update := mgutils.Set(bson.M{
		tripField:                trip,
		mgutils.UpdatedFieldName: newUpdatedAt,
	})
	_, err := m.col.UpdateOne(ctx, filter, update)
	if err != nil {
		return err
	}
	return err
}
